package thread;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2023-12-15
 * Time: 20:30
 */
 /*
    线程安全问题[整个线程中,最难的一部分,也是最重要的一部分]
    引入多线程,目的是为了实现 "并发编程"
    实现"并发编程",也不仅仅只依靠多线程
    解决方案是很多的
    相比之下,多线程,属于一种比较原始,也比较朴素的方案.(问题 和 注意事项,就是比较多的)
    及其容易出错.
    什么是线程安全?
    某个代码,无论是在单个线程下执行,还是多个线程下执行,都不会产生 bug.这个情况就称为"线程安全"
    如果这个代码,单线程下运行正确.但是多线程下,就可能会产生 bug.这个情况就称为 "线程不安全" 或者 "线程安全问题"
    接下来是一个 线程安全问题 的典型案例
    */
public class ThreadDemo18 {
    private static int count = 0;

    //如果两个锁用不同的对象,也会出现线程竞争问题.
    public static void main5(String[] args) throws InterruptedException {
        //创建两个线程,每个线程都针对上述 count 变量循环自增 5W 次
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (locker1) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                synchronized (locker2) {
                    count++;
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();


        System.out.println("count = " + count);

    }


    //如果给一个线程加锁,一个不加锁是否会有线程安全问题?
    //不会出现锁竞争就会出现线程安全问题
    public static void main4(String[] args) throws InterruptedException {
        //创建两个线程,每个线程都针对上述 count 变量循环自增 5W 次
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();


        System.out.println("count = " + count);

    }


     /*为什么会出现下边不能计算正确这个问题?
        站在cpu指令上看,是由三个 cpu指令构成的.
        1) load 从内存中读取数据到 cpu 的寄存器
        2) 把寄存器里面的值 +1
        3) save 把寄存器的值写会到 内存中
        如果是一个线程执行上述的 三个 指令,是不存在问题的
        如果是两个线程,并发的执行上述操作,此时就会存在变数 (线程之间的调度是不确定的 : 随机性)
        线程有随机调度/抢占式执行



        为什么会出现"线程不安全"?
        1)[根本原因]本质上是因为线程之间的调度是"无序"的(抢占式执行)
         "随机调度" => 线程之间执行的顺序带来了很多的变数(罪魁祸首 万恶之源)
        2)代码结构.代码中的多个线程,同时修改同一个变量
           1.一个线程修改一个变量,不存在问题
           2.多个线程读取同一个变量,不存在问题  如果只是读取,变量的内容,固定不变的 不牵扯线程安全问题
           3.多个线程修改不同的变量,不存在问题  (不同变量,彼此之间就不会产生相互覆盖的情况了)
        3)[直接原因] main1 多线程修改操作,本身不是 "原子的"
                   count++
                             是多个 cpu指令 就可能会存在一下的情况:
                                          一个线程执行这些指令,执行到一半,就可能被调度走,
                                          从而其他线程会有可能"趁虚而入"
        4)内存可见性问题
        5)指令重排序


           eg:
           String 是一个"不可变的对象"                                          要怎样实现一个 不可变 对象,应该怎么做?
           有啥好处?                                                           1) 持有的数据(byte 数组)是 private 的
           1.方便 JVM 进行缓存(放到字符串常量池中)                                  2) 没有提供 public 的方法来修改 byte 数组的内容
           2.hash 值固定的                                                       final 只是不能被继承 和可变没有太大的关系
           3.线程是安全的                                                         final 本身存在多种功能
           有的编程语言,为了解决上述这种线程安全问题,直接不去引"变量"这个概念
           (所有的 量 都是 不可变 的)
           此时这样的代码,写出来,更容易保证线程安全



        如何解决这个问题?
        需要解决的问题是,
        在无数种情况下,代码都得是正确的
        在无数种情况下,只有一类方式是符合于预期的.
        就是指 cpu三条指令 捆绑在一起的时候这个是可以执行的

        知道原因,就可以做到对症下药

        针对 1) 无法做出改变,系统内部已经实现了 抢占式 执行,无法对其作出做出改变
                                         如果改变了操作系统内核的实现,理论上是可以实现的
                                         但是一旦修改内核,就是"新的系统"了
        针对 2) 分情况 {一部分代码结构可以调整
                     {一部分代码结构就是调整不了

        针对 3) count++ 被生成几个指令,看起来我们无法进行干预
                但实际上,还是有办法的 ,可以通过特殊的指令,把这三个指令打包到一起,成为"整体"
                这个操作,被称为"加锁"
                "锁" 具有 "互斥" "排他" 这种特性
                                             类似于某饭店在饭店人满的时候,
                     互斥,即为别人使用不了       需要等待上一个桌子上的人吃完饭,
                                             下一个排队的人才有机会吃上饭
                  在java中, 加锁方式有好几种
                   最主要的方式就是使用 synchronized 关键字
                   synchronized 本质上是调用系统的 api 进行加锁
                                          系统的 api 本质上是靠 CPU上的特定指令来完成加锁的

        */
     public static void main3(String[] args) throws InterruptedException {
         /* 加锁的目的是为了把 count++ 在cpu上的三条指令的操作,打包成一个 原子 的操作
            但是很明显，并非是加锁之后，执行这个操作过程中，线程就不调度了
            执行过程中 t1 任然会被调度出cpu
            但是即使被调度走了，其他线程也无法 “插队执行” */

            /*
            创建对象 但必须是一个对象.可以是任意对象,但是需要注意的是
              是否是同一个对象?
               是就会产生竞争,不是就不会产生竞争
               针对不同对象加锁,上述锁竞争就不会发生.线程安全问题就不会存在.
            */
         Object o = new Object();
         Thread t1 = new Thread(() -> {
             for (int i = 0; i < 50000; i++) {
                 synchronized (o) {    /*进入 {}就会加锁(o)*/
                     count++;
                 }                     /* 出了 {} 就会解锁*/
             }
         });
         Thread t2 = new Thread(() -> {
             for (int i = 0; i < 50000; i++) {
                 synchronized (o) {
                     count++;
                 }
             }
         });
         /*
           加锁之后 并发执行的速度变慢了,但是保证了准确性.new 他也比串行执行的效率会高很多
           此处的锁只给 count++ 带上了锁.所以只有for循环里边是串行执行,其他都是并发执行的.
         */
             t1.start();
             t2.start();    /*
                              t1 先执行 lock,加锁可以成功,
                              t2后执行 lock的时候,由于锁已经被 t1给加上了.
                              t2就会阻塞等待 暂时无法在cpu上执行，接下来 t2 的三步操作，是无法插队的
                              这样就能确保，t2的 load 能够在 t1 save 之后执行，结果就不会收到影响
                             */

             t1.join();
             t2.join();
         }
     public static void main2(String[] args) throws InterruptedException {
        /* 加锁的目的是为了把 count++ 在cpu上的三条指令的操作,打包成一个 原子 的操作 */
         Thread t1 = new Thread(() -> {
             for (int i = 0; i < 50000; i++) {
                     count++;
                 /* for (int i = 0; i < 50000; i++) {
                  *//*
                synchronized {
                    count++;
                 }
                这里的操作是有问题的,加锁的时候,需要先准备好 "锁对象"
                加锁,解锁操作,都是依托于这里的 "锁对象" 来展开的

                如果一个线程,针对一个线程加上锁之后,
                其他线程,也尝试对这个对象加锁,就会产生阻塞 (BLOCKED)   这里的阻塞 --> 就被称为 锁冲突/锁竞争
                一直阻塞到,前一个线程释放锁为止

                进一步展开,如果两个线程,是针对不同的对象加锁
                此时就不会有锁竞争,也就是不会有阻塞

                                             类似于某饭店在饭店人满的时候,
                     拿吃饭来举例子             需要等待上一个桌子上的人吃完饭,
                                             下一个排队的人才有机会吃上饭

                            这个时候就有两种情况了
                            1) 阻塞等待(愿意饿着肚子等下去) 等到时机成熟了,起那一桌的人解决完了(锁被释放了),我就有机会吃上饭了
                            2) 直接放弃
                         java 中的 synchronized 做法,就是 "甘当备胎"
                    但是当第一个饭店人满的时候,我选择另一个人少的饭馆 吃饭的时候,会影响我吃不到饭吗?
                    当然不会,因为饭店是一个 "锁对象", 饭馆是另一个 "锁对象"
                    一旦我先一步其他人进入了饭馆,恰好人满了.哪么此时其他需要吃饭的人就需要等待/(放弃)换下个餐馆
                *//*
                 synchronized {
                     count++;
                 }
             }*/
             }
         });
         Thread t2 = new Thread(() -> {
             for (int i = 0; i < 50000; i++) {
                 count++;
             }
         });

         t1.start();
         t2.start();

         t1.join();
         t2.join();
     }
    public static void main1(String[] args) throws InterruptedException {
        //创建两个线程,每个线程都针对上述 count 变量循环自增 5W 次
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        //打印count结果
        /*
        按理来说,此处自增 5W 次,结果就应该是 10W
        但是结果不是 10W,且每次运行的结果都不同
        但是结果不符合预期,就是 bug
        */

        System.out.println("count = " + count);
        //上述 循环自增的代码 就属于是"存在线程安全"的代码


    }
}
